 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.expressions;

 import java.util.ArrayList ;
 import java.util.HashMap ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IExtensionDelta;
 import org.eclipse.core.runtime.IExtensionRegistry;
 import org.eclipse.core.runtime.IRegistryChangeEvent;
 import org.eclipse.core.runtime.IRegistryChangeListener;
 import org.eclipse.core.runtime.Platform;

 import org.eclipse.core.expressions.IPropertyTester;

 public class TypeExtensionManager implements IRegistryChangeListener {
     
     private String fExtensionPoint;
     
     private static final String TYPE= "type"; //$NON-NLS-1$

     private static final IPropertyTester[] EMPTY_PROPERTY_TESTER_ARRAY= new IPropertyTester[0];
     
     private static final IPropertyTester NULL_PROPERTY_TESTER= new IPropertyTester() {
         public boolean handles(String namespace, String property) {
             return false;
         }
         public boolean isInstantiated() {
             return true;
         }
         public boolean isDeclaringPluginActive() {
             return true;
         }
         public IPropertyTester instantiate() throws CoreException {
             return this;
         }
         public boolean test(Object receiver, String property, Object [] args, Object expectedValue) {
             return false;
         }
     };
     
     /*
      * Map containing all already created type extension object.
      */
     private Map /*<Class, TypeExtension>*/ fTypeExtensionMap;
     
     /*
      * Table containing mapping of class name to configuration element
      */
     private Map /*<String, List<IConfigurationElement>>*/ fConfigurationElementMap;
     
     /*
      * A cache to give fast access to the last 1000 method invocations.
      */
     private PropertyCache fPropertyCache;
     
     
     public TypeExtensionManager(String extensionPoint) {
         Assert.isNotNull(extensionPoint);
         fExtensionPoint= extensionPoint;
         Platform.getExtensionRegistry().addRegistryChangeListener(this);
         initializeCaches();
     }

     public Property getProperty(Object receiver, String namespace, String method) throws CoreException {
         return getProperty(receiver, namespace, method, false);
     }
     
     public synchronized Property getProperty(Object receiver, String namespace, String method, boolean forcePluginActivation) throws CoreException {
         long start= 0;
         if (Expressions.TRACING)
             start= System.currentTimeMillis();
         
         // if we call a static method than the receiver is the class object
 Class clazz= receiver instanceof Class ? (Class )receiver : receiver.getClass();
         Property result= new Property(clazz, namespace, method);
         Property cached= fPropertyCache.get(result);
         if (cached != null) {
             if (cached.isValidCacheEntry(forcePluginActivation)) {
                 if (Expressions.TRACING) {
                     System.out.println("[Type Extension] - method " + //$NON-NLS-1$
 clazz.getName() + "#" + method + //$NON-NLS-1$
 " found in cache: " + //$NON-NLS-1$
 (System.currentTimeMillis() - start) + " ms."); //$NON-NLS-1$
 }
                 return cached;
             }
             // The type extender isn't loaded in the cached method but can be loaded
 // now. So remove method from cache and do the normal look up so that the
 // implementation class gets loaded.
 fPropertyCache.remove(cached);
         }
         TypeExtension extension= get(clazz);
         IPropertyTester extender= extension.findTypeExtender(this, namespace, method, receiver instanceof Class , forcePluginActivation);
         if (extender == TypeExtension.CONTINUE || extender == null) {
             throw new CoreException(new ExpressionStatus(
                 ExpressionStatus.TYPE_EXTENDER_UNKOWN_METHOD,
                 Messages.format(
                     ExpressionMessages.TypeExtender_unknownMethod,
                     new Object [] {method, clazz.toString()})));
         }
         result.setPropertyTester(extender);
         fPropertyCache.put(result);
         if (Expressions.TRACING) {
             System.out.println("[Type Extension] - method " + //$NON-NLS-1$
 clazz.getName() + "#" + method + //$NON-NLS-1$
 " not found in cache: " + //$NON-NLS-1$
 (System.currentTimeMillis() - start) + " ms."); //$NON-NLS-1$
 }
         return result;
     }
     
     /*
      * This method doesn't need to be synchronized since it is called
      * from withing the getProperty method which is synchronized
      */
     /* package */ TypeExtension get(Class clazz) {
         TypeExtension result= (TypeExtension)fTypeExtensionMap.get(clazz);
         if (result == null) {
             result= new TypeExtension(clazz);
             fTypeExtensionMap.put(clazz, result);
         }
         return result;
     }
     
     /*
      * This method doesn't need to be synchronized since it is called
      * from withing the getProperty method which is synchronized
      */
     /* package */ IPropertyTester[] loadTesters(Class type) {
         if (fConfigurationElementMap == null) {
             fConfigurationElementMap= new HashMap ();
             IExtensionRegistry registry= Platform.getExtensionRegistry();
             IConfigurationElement[] ces= registry.getConfigurationElementsFor(
                 ExpressionPlugin.getPluginId(),
                 fExtensionPoint);
             for (int i= 0; i < ces.length; i++) {
                 IConfigurationElement config= ces[i];
                 String typeAttr= config.getAttribute(TYPE);
                 List typeConfigs= (List )fConfigurationElementMap.get(typeAttr);
                 if (typeConfigs == null) {
                     typeConfigs= new ArrayList ();
                     fConfigurationElementMap.put(typeAttr, typeConfigs);
                 }
                 typeConfigs.add(config);
             }
         }
         String typeName= type.getName();
         List typeConfigs= (List )fConfigurationElementMap.get(typeName);
         if (typeConfigs == null)
             return EMPTY_PROPERTY_TESTER_ARRAY;
         else {
             IPropertyTester[] result= new IPropertyTester[typeConfigs.size()];
             for (int i= 0; i < result.length; i++) {
                 IConfigurationElement config= (IConfigurationElement)typeConfigs.get(i);
                 try {
                     result[i]= new PropertyTesterDescriptor(config);
                 } catch (CoreException e) {
                     ExpressionPlugin.getDefault().getLog().log(e.getStatus());
                     result[i]= NULL_PROPERTY_TESTER;
                 }
             }
             fConfigurationElementMap.remove(typeName);
             return result;
         }
     }
     
     public void registryChanged(IRegistryChangeEvent event) {
         IExtensionDelta[] deltas= event.getExtensionDeltas(ExpressionPlugin.getPluginId(), fExtensionPoint);
         if (deltas.length > 0) {
             initializeCaches();
         }
     }
     
     private synchronized void initializeCaches() {
         fTypeExtensionMap= new HashMap ();
         fConfigurationElementMap= null;
         fPropertyCache= new PropertyCache(1000);
     }
 }

